# REST framework之版本和解析器

# 版本(获取URL中传入的版本)

  • 简单通过url携带的参数,在后端进行获取

     #url.py文件中
    from django.conf.urls import url
    from api import views
    urlpatterns = [
        url(r'users/$',views.UsersView.as_view())
    ]
    
      # views.py文件中
    from django.shortcuts import render,HttpResponse
    from rest_framework.views import APIView
    
    class UsersView(APIView):
        def get(self,request, *args, **kwargs):
            #version= request._request.GET.get('version')  # 通过_request获得参数
            version= request.query_params.get('version')
            print(version)
            return HttpResponse('用户列表')
            
    
    • 小提示:

      1、通过rest_framework封装的reuqest函数,可以调用query_params属性获得原有request的参数
      from rest_framework.request import Request
      ...
        @property
          def query_params(self):   # 该函数用于返回原有的request的所有参数
              return self._request.GET
      

# 1、简单自定义版本获取类(GET传参:URL后?version=v1)

from django.shortcuts import render,HttpResponse
from rest_framework.request import Request
from rest_framework.views import APIView

class ParamVersion:
    def determine_version(self,request,*args,**kwargs):
        version = request.query_params.get('version')
        return version

class UsersView(APIView):
    versioning_class = ParamVersion  # 不再是一个列表了,是一个类
    def get(self,request, *args, **kwargs):
        print(request.version)   # 这样就可以获取url中的version对应得值了, api/users/?version=1
        return HttpResponse('用户列表')
# RESTframework自带得版本获取器
from rest_framework.views import APIView
from rest_framework.versioning import QueryParameterVersioning

class UsersView(APIView):  
    versioning_class = QueryParameterVersioning  # 直接就可以用了
    def get(self,request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')
  • # 源码分析:
    点击QueryParameterVersioning,查看原有代码,设计三个配置项,如下图
    
    class BaseVersioning:
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    
        def determine_version(self, request, *args, **kwargs):
            msg = '{cls}.determine_version() must be implemented.'
            raise NotImplementedError(msg.format(
                cls=self.__class__.__name__
            ))
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            return _reverse(viewname, args, kwargs, request, format, **extra)
    
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))
        
    
    class QueryParameterVersioning(BaseVersioning):
        invalid_version_message = _('Invalid version in query parameter.')
    
        def determine_version(self, request, *args, **kwargs):
            version = request.query_params.get(self.version_param, self.default_version)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            url = super().reverse(
                viewname, args, kwargs, request, format, **extra
            )
            if request.version is not None:
                return replace_query_param(url, self.version_param, request.version)
            return url
      
    
    • 小提示:

      1、 self.version_param: #表示version得名称是什么?
          self.default_version: # 默认版本
      	self.is_allowed_version: #用户判断是否是被允许的版本号
      
# 在settings中配置相关参数
REST_FRAMEWORK = {
    'DEFAULT_VERSION':'v1',  #默认v1,不传的话
    'ALLOWED_VERSIONS':['v1','v2'],
    'VERSION_PARAM':'version'  # 允许得key;version=v1
}

# 2、在URL中传参 (如:api/v1,局部)

通过在URLPathVersioning类进行版本获取

 # url.py文件中
urlpatterns = [
    url(r'(?P<version>[v1|v2]+)/users/$',views.UsersView.as_view())
]

 # 视图函数中
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning

class UsersView(APIView):   
    versioning_class = URLPathVersioning
    def get(self,request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表') 
  • 小提示:

    以上的方法对:8000/api/v1/users/ --> 将会取出v1,注意也是要集合配置了settings中的配置REST_FRAMEWORK使用
    
# 全局配置
1、只需要在settings中的配置项中添加,(在APIView中可以看见有个DEFAULT_VERSION_CLASS的选项)
    REST_FRAMEWORK = {
        'DEFAULT_VERSION':'v1',
        'ALLOWED_VERSIONS':['v1','v2'],
        'VERSION_PARAM':'version',  # 允许得key;version=v1
        'DEFAULT_VERSIONING__CLASS':'rest_framework.versioning.URLPathVersioning',
    } # 这样视图函数中什么也不用写了,所有的视图函数都可获取version了

# 版本组件源码流程
1、依旧从dispatch进入,封装request后,执行initial函数,在走认证之前
		def determine_version(self, request, *args, **kwargs):
            if self.versioning_class is None:
                return (None, None)
            scheme = self.versioning_class()  # versioning_class就是我们setting中配置的类的对象
            return (scheme.determine_version(request, *args, **kwargs), scheme) #第一个返回版本

		...
	    def initial(self, request, *args, **kwargs):
            self.format_kwarg = self.get_format_suffix(**kwargs)

            neg = self.perform_content_negotiation(request)
            request.accepted_renderer, request.accepted_media_type = neg
            
            version, scheme = self.determine_version(request, *args, **kwargs) #处理版本对象
			request.version, request.ver sioning_scheme = version, scheme
			
            self.perform_authentication(request)  # 认证
            self.check_permissions(request)  # 权限
        	self.check_throttles(request)  # 节流
  • 小提示:

    from rest_framework.views import APIView
    
    class UsersView(APIView):
        def get(self,request, *args, **kwargs):
            # 获取版本
            print(request.version)
            # 获取处理版本的对象
            print(request.versioning_scheme) #<rest_framework.versioni58676438>
            u1 = request.versioning_scheme.reverse(viewname='uuu',request=request)
            print(u1) # http://127.0.0.1:8000/api/v1/users/
            return HttpResponse('用户列表')
    

# 版本总结

1、配置settings.py
    REST_FRAMEWORK = {
        'DEFAULT_VERSION':'v1',
        'ALLOWED_VERSIONS':['v1','v2'],
        'VERSION_PARAM':'version',  # 允许得key;version=v1
        'DEFAULT_VERSIONING__CLASS':'rest_framework.versioning.URLPathVersioning',
    } # 这样视图函数中什么也不用写了,所有的视图函数都可获取version了

2、视图中,只要没有重写versioning_class,那么就可以通过全局获取version

# 解析器(依靠请求头的Content-Type对请求体中的数据解析到request.data中)

  • 前戏

    django中:request.POST / request.body的区分?
    	我们可以来打印以下django中的request的类型,发现他是:django.core.handlers.wsgi.WSGIRequest的对象,我们点开WSGIRequeset来查看里面的方法,我们找到POST,点击_get_post方法-->再点击_load_post_and_files,在这个方法中有:
    	if self.content_type == 'multipart/form-data':pass  # 表示上传文件
    	elif self.content_type == 'application/x-www-form-urlencoded':pass # 如果content_type是这个头,那么我们的request.POST中才有数据,并且是个QueryDict(去request.body中解析)
      
    1、要求请求头内容格式为:Content-Type:application/x-www-form-urlencoded 
    2、数据格式要求:name=alex&age=19&gender=

如果上面两个要求都满足了,那么request.POST中才有值

如: a. form表单提交,默认的请求头内容格式为:Content-Type:application/x-www-form-urlencoded <form method ...> # 这里可以更改请求头内容格式 ...

  b. ajax提交,将下面的data转换为name=alex&age=19&gender=男类型,并带有Content-Type:application/x-www-form-urlencoded传到后端
  
      $.ajax({
          url:...,
          type:POST,
          data:{name:alex,age:18}
      })  # 默认这种传参会自动转换格式为name=alex&age=19&gender=男,所以POST中有数据

      # ajax定制请求头内容格式
      $.ajax({
              url:...,
              type:POST,
          	headers:{'Content-Type':'application/json'}  #这种情况request.POST没有数据,body有
              data:{name:alex,age:18}
          })
      
      #ajax更改请求头,同时更改了data数据
       $.ajax({
              url:...,
              type:POST,
          	headers:{'Content-Type':'application/json'},  #这种情况request.POST没有数据,body有
              data:JSON.stringfy({name:alex,age:18}), # 将字典转换为字符串了,格式也不是之前的格式
          })  # body有值"{'name':'alex','age':'18'}",POST没有值         

- **小提示:**

  ```
  通过上面的知识我们知道了,django对提交的数据只支持满足两种条件才能取出值,(1,固定的请求头格式;2,固定的传参方式);所以我们需要一种解析器,用来解析数据。
  
  也是就说上面我们发的json数据,不满足传参格式,必须在body中获取并转换才行,比较麻烦
  ```

  ![1564141470719](C:\Users\RootUser\Desktop\知识点复习\Django\gif\1564141470719.png)

#### REST framework解析器

##### 简单使用解析器

```python
from rest_framework.parsers import JSONParser,FormParser
class ParserView(APIView):
  parser_classes = [JSONParser,FormParser,]
  def post(self, request, *args, **kwargs):
      '''
      允许用户发送JSON格式数据
          a.content-type:application/json
          b.{'name':'alex','age':18}
      JSONParser:表示只能解析content-type:application/json头的数据
      FormParser:表示只能解析Content-Type:application/x-www-form-urlencoded头的数据
      '''
      print(request.data)
      ret = {}
      ret['name1'] = request.data['name']
      return JsonResponse(ret)

# 在postman的请求体中发送json数据:
      {
          "name":"alex",
          "age":18
      }
# 返回:{"name1": "alex"}

  • 总结:

    如果不使用解析器,那么后端拿到的数据是不会出现在POST中的,且格式混乱。
    
# 解析器流程分析
1、进入Request类,找到data属性
	 @property
      def data(self):
          if not _hasattr(self, '_full_data'):
              self._load_data_and_files()  #此函数用于返回加载的数据
          return self._full_data
2、进入_load_data_and_files函数后,执行self._parse() 函数,在此函数中
	2.1、media_type = self.content_type  # 获取用户请求头格式
    
    2.2、parser = self.negotiator.select_parser(self,self.parsers) # parses是自己类中写的解析器列表,self是请求对象,包含了self.content_type,进行匹配,选择parser,即该选那个解析器
    
    2.3、parsed = parser.parse(stream, media_type, self.parser_context) #执行各自的parse方法

3、假如此时进入的是JSONParser类中parser方法
	 def parse(self, stream, media_type=None, parser_context=None):
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)

        try:
            decoded_stream = codecs.getreader(encoding)(stream)  # 获取请求体
            parse_constant = json.strict_constant if self.strict else None
            return json.load(decoded_stream, parse_constant=parse_constant) # 加载数据
        except ValueError as exc:
            raise ParseError('JSON parse error - %s' % str(exc))
            
4、执行dispatch方法的是有,在封装Request的时候
	def initialize_request():
        ...
        return Request(
                request,
                parsers=self.get_parsers(),  # 这里触发一个列表生成式,将每个解析器封装在request中
                authenticators=self.get_authenticators(), #认证对象
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
# 全局配置解析器
 通过上面的分析,我们也就可以在全局配置解析器器
    REST_FRAMEWORK = {
        'DEFAULT_VERSION':'v1',
        'ALLOWED_VERSIONS':['v1','v2'],
        'VERSION_PARAM':'version',  # 允许得key;version=v1
        'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
        'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser', # 配置解析器
        							'rest_framework.parsers.FormParser'],
    } 
    
 # 也就是说我们的视图函数中只需要request.data即可
class ParserView(APIView):
    def post(self, request, *args, **kwargs):
        print(request.data)
        ret = {}
        ret['name1'] = request.data['name']
        return JsonResponse(ret)
  
# 总结:
1、直接在全局settings中配置DEFAULT_PARSER_CLASSES,和解析器即可
2、如果某视图项逃逸某个解析器,既可以重新选择自己的解析器
# 源码流程 & 本质
本质:
	请求头:
		1.1: Accept:浏览器可接受的MIME类型。
		1.2: Accept-Charset:浏览器可接受的字符集。
		1.3: Content-Length:表示请求消息正文的长度。
		1.4: Cookie:这是最重要的请求头信息之一
		1.5: Host:初始URL中的主机和端口。
		1.6: User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
		1.7: Content-Type 表示后面的文档属于什么MIME类型
	状态码:
		2.2: 200 (成功) 服务器已成功处理了请求。 通常。
		2.3: 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
		2.4: 400 (错误请求) 服务器不理解请求的语法。
		2.5: 403 (禁止) 服务器拒绝请求。可能是Forbiend,可能是crsf
		2.6: 404 (未找到) 服务器找不到请求的网页。
		2.7: 500 (服务器内部错误) 服务器遇到错误,无法完成请求。
		2.8: 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

请求方法:
1、GET方法
 	GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
说明:
	GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中;且GET请求有长度限制,仅用于请求数据(不修改)。注:因GET请求的不安全性,在处理敏感数据时,绝不可以使用GET请求。

2、POST方法
	POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。

3、HEAD方法
	HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。

4、PUT方法
	PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
	它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。

5、DELETE方法
	DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。

6、CONNECT方法
	CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。

7、OPTIONS方法
	OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。

8、TRACE方法
	TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。